class Compiler {
  constructor(vm) {
    this.vm = vm
    this.el = vm.$el
    this.compile(this.el)
  }

  /* 编译模板，处理文本节点 & 元素节点 */
  compile(el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      if (this.isTextNode(node)) {
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        this.compileElement(node)
      }
      // 判断节点内部是否有子节点
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }

  /* 编译文本节点 */
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/
    const value = node.textContent
    if (reg.test(value)) {
      const key = RegExp.$1.trim()
      node.textContent = node.textContent.replace(reg, this.vm[key])
    
      new Watcher(this.vm, key, newVal => {
        node.textContent = newVal
      })
    }
  }

  /* 编译元素节点 */
  compileElement(node) {
    // console.log(node.attributes, 'attributes')
    Array.from(node.attributes).forEach(attr => {
      let attrName = attr.name
      let eventName = null
      let key = null // vm 上的属性或方法名
      if (this.isDirective(attrName)) {
        attrName = attrName.substr(2)
        key = attr.value
        // v-on:click
        if (attrName.indexOf(':') > -1) {
          const attrArr = attrName.split(':')
          attrName = attrArr[0] // on
          eventName = attrArr[1] // click
        }
        this.update(node, key, attrName, eventName)
      } else if (this.isDirectiveAbbreviation(attrName)) {
        // @click
        const prefix = attrName.slice(0, 1) // 前缀
        if (prefix === '@') {
          eventName = attrName.substr(1)
          attrName = 'on'
        }
        key = attr.value
        this.update(node, key, attrName, eventName, true)
      }
    })
  }

  /**
   * 调用指令的方法
   * @param {*} node 
   * @param {*} key vm 上的属性名 or 方法名
   * @param {*} attrName model / text / on ...
   * @param {*} eventName 事件名称 click / change ...
   * @param {*} isAbbreviation 是否是缩写
   */
  update(node, key, attrName, eventName, isAbbreviation) {
    const updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key], key, attrName, eventName, isAbbreviation)
  }

  /* v-text */
  textUpdater(node, value, key, attrName) {
    node.textContent = value
    node.removeAttribute(`v-${attrName}`)

    new Watcher(this.vm, key, newVal => {
      node.textContent = newVal
    })
  }

  /* v-model */
  modelUpdater(node, value, key, attrName) {
    node.value = value
    node.removeAttribute(`v-${attrName}`)

    new Watcher(this.vm, key, newVal => {
      node.value = newVal
    })

    // 双向绑定，视图更新数据
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  /* v-html */
  htmlUpdater(node, value, key, attrName) {
    node.innerHTML = value
    node.removeAttribute(`v-${attrName}`)

    new Watcher(this.vm, key, newVal => {
      node.innerHTML = newVal
    })
  }

  // v-on or @
  onUpdater(node, value, key, attrName, eventName, isAbbreviation) {
    console.log(eventName, value, 'eventName')
    node.addEventListener(eventName, value)
    if (isAbbreviation) {
      node.removeAttribute(`@${eventName}`)
    } else {
      node.removeAttribute(`v-${attrName}:${eventName}`)
    }
  }

  /* 判断是否是指令 v- 开头 */
  isDirective(attr) {
    return attr.startsWith('v-')
  }
  /* 是否是指令缩写 v-on => @ */
  isDirectiveAbbreviation(attr) {
    return attr.startsWith('@')
  }
  /* 判断是否是文本节点 */
  isTextNode(node) {
    return node.nodeType === 3
  }
  /* 判断是否是元素节点 */
  isElementNode(node) {
    return node.nodeType === 1
  }
}